package wci.backend.compiler;

import java.util.ArrayList;
import java.io.*;

import wci.frontend.*;
import wci.intermediate.*;
import wci.intermediate.symtabimpl.Predefined;
import wci.backend.*;

import static wci.intermediate.symtabimpl.SymTabKeyImpl.*;
import static wci.intermediate.symtabimpl.DefinitionImpl.*;

/**
 * <p>The code generator for a compiler back end.</p>
 *
 * <p>Copyright (c) 2008 by Ronald Mak</p>
 * <p>For instructional purposes only.  No warranties.</p>
 */
public class CodeGenerator extends Backend
{
    public static final int STACK_LIMIT = 16;
        
    static ICode iCode;
    static SymTabStack symTabStack;
    static PrintWriter objectFile;
    static String programName;

    public enum MethodType
    {
    	Instance,
    	Shared
    }
    public static int GetVarIndex(int varIndex, MethodType type)
    {
    	if (type == MethodType.Instance)
    	{
    		return  varIndex +1;
    	}
    	else
    	{
    		return varIndex;
    	}
    }
    
public static String GetTypeCode(TypeSpec type)
{
	return ("L" + GetTypeCodeWithoutL(type) + ";");
}
 public static   String GetTypeCodeWithoutL(TypeSpec type)
    {
	 	/*
	 	 * Add all of our other types here!
	 	 */
    	if (type ==  Predefined.integerType)
    	{
    		return "java/lang/Integer";
    	}
    	else if (type == Predefined.stringConstantType || type == Predefined.stringType) {
    		return "java/lang/String";
    	}
    	else if (type == Predefined.variantType)
    	{
    		return "java/lang/Object";
    	}
    	else if (type == Predefined.listType) {
    		return "java/util/List";
    	}
    	else
    	{
    		return "java/lang/Object";
    	}
    }
 public static String GetTypePrefix(TypeSpec type)
 {
	 //TODO: add the rest of the prefixes
		if (type ==  Predefined.integerType ) //|| type == Predefined.realType )
    	{
    		return "a";	//now using class Integer instead of int
    	}
    	else
    	{
    		return "a";
    	}
 }
    
    /**
     * Process the intermediate code and the symbol table generated by the
     * parser to generate machine-language instructions.
     * @param iCode the intermediate code.
     * @param symTabStack the symbol table stack.
     * @param objectFile the object file path for the generated code.
     * @throws Exception if an error occurred.
     */
    public void process(ICode iCode, SymTabStack symTabStack,
                        String objectFilePath)
        throws Exception
    {
        CodeGenerator.iCode       = iCode;
        CodeGenerator.symTabStack = symTabStack;
        CodeGenerator.objectFile  = new PrintWriter(objectFilePath);

        // Make the program and method names.
        int start = objectFilePath.lastIndexOf("/") + 1;
        programName = objectFilePath.substring(start);
        int end = programName.indexOf(".");
        if (end > -1) {
            programName = programName.substring(0, end);
        }
        programName = programName.substring(0, 1).toUpperCase() +
                      programName.substring(1);
        String methodName = programName.substring(0, 1).toLowerCase() +
                            programName.substring(1);
        
        SymTabEntry programId = symTabStack.getProgramId();
        int localsCount = 
                (Integer) programId.getAttribute(ROUTINE_LOCALS_COUNT);
        SymTab routineSymTab = 
                (SymTab) programId.getAttribute(ROUTINE_SYMTAB);
        ArrayList<SymTabEntry> locals = routineSymTab.sortedEntries();

        // Generate the header code.
        objectFile.println(".class public " + programName);
        objectFile.println(".super scarlett/ScarlettRuntime");
        
       // 
        
        
        objectFile.println("; standard initializer");
        objectFile.println(".method public <init>()V");
		objectFile.println("   aload_0");
		 
		objectFile.println("   invokenonvirtual scarlett/ScarlettRuntime/<init>()V");
		objectFile.println("   return");
		objectFile.println(" .end method");
		
		processMethodBodies();
		
		objectFile.println(".method public static main([Ljava/lang/String;)V");
		objectFile.println("    .limit stack 16");
		objectFile.println("    .limit locals 2");
		//objectFile.println("   aload_0");
		objectFile.println("    new " + programName);
		objectFile.println("    dup");
		objectFile.println("    invokenonvirtual " + programName + "/<init>()V");
   //Ljava/lang/String;II
		//
		objectFile.println("    aload_0");
		objectFile.println("    invokenonvirtual " + programName + "/ScarlettMain([Ljava/lang/String;)V");
		
		objectFile.println("	return");
		objectFile.println(".end method");

		objectFile.println();
        objectFile.println(".method public  run([Ljava/lang/String;)V");
        
      //  objectFile.println("	getstatic java/lang/System/out Ljava/io/PrintStream;");
        
        // Generate the .var lines.
        for (SymTabEntry id : locals) {
            Definition defn = id.getDefinition();
            
            if ((defn == VARIABLE) || (defn == VAR_PARM) || (defn == VALUE_PARM)) {
                String localName = id.getName();
                int localIndex = GetVarIndex(id.getIndex(), MethodType.Instance);
                TypeSpec type = id.getTypeSpec();                
                objectFile.println(".var " + localIndex + " is " +
                                   localName + " " + GetTypeCode(type));
            }
        }
        
        for (SymTabEntry id : locals) {
        	Definition defn = id.getDefinition();
            
            if ((defn == VARIABLE) || (defn == VAR_PARM) || (defn == VALUE_PARM)) {
                int localIndex = GetVarIndex(id.getIndex(), MethodType.Instance);
                TypeSpec type = id.getTypeSpec(); 
                objectFile.println("    aconst_null");
                objectFile.println("    astore " + localIndex);
            }
        }
        
        objectFile.println();
        objectFile.flush();

        CodeInfo ci = new CodeInfo();
        ci.type = MethodType.Instance;
        // Visit the parse tree nodes to generate code.
        CodeGeneratorVisitor codeVisitor = new CodeGeneratorVisitor();
        Node rootNode = iCode.getRoot();
        rootNode.jjtAccept(codeVisitor, ci);

        //objectFile.println("invokenonvirtual " + programName + "/show(Ljava/lang/String;)V");
        
        //objectFile.println("	invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V");
        
        // Generate the footer code.
        objectFile.println("    return");
        objectFile.println();
        objectFile.println(".limit locals " + (locals.size()+5));//this is funky, we are not tracking this count correctly yet cward 5/15/2011);
        objectFile.println(".limit stack  " + STACK_LIMIT);
        objectFile.println(".end method");
        objectFile.flush();

        CodeGenerator.objectFile.close();
    }
    
	private void processMethodBodies() {
		// TODO: What is the purpose of CodeInfo?
		CodeInfo ci = new CodeInfo();
        ci.type = MethodType.Instance;
        // This will only visit the method body nodes
        MethodVisitor methodVisitor = new MethodVisitor();
        Node rootNode = iCode.getRoot();
        rootNode.jjtAccept(methodVisitor, ci);
	}
}
